I haven't updated this journal in a long time, and mostly been writing in my LiveJournal Technical blog. I've emailed to the Perlsphere maintainer about including that blog there (once from my home address and once from gmail.com), but received no reply, and so far it is not included there.
In any case, this post is about File-Find-Object, which is an object-oriented alternative to the core File::Find module. Originally by Nanardon, I've maintained it since version 0.0.3.
Recently, I've been meaning to write a project, and contemplated whether to use File-Find-Object or roll-out my own directory traversal logic, but then decided that improving F-F-O so it will do what I want was a better idea, in an "Eating one's own dog food" and not-re-inventing the wheel fashion. So I needed to extend F-F-O to do what I want.
While I took a closer look at the code to inspect it, I found it had some "bad smells", and decided to fix it by refactoring, as a necessary pre-requisite for extending it.
The first thing I did was notice that many methods accepted a $current parameter that was passed from one method to another, and then used. As it turned out, most of these simply originate from $self->_current(), which I now used to retrieve the value in each method, without passing a parameter.
Another fact I noticed was that there were many if ($self eq $current) checks in the code. Since $current is dynamic, I decided to create a predicate _is_top() which will encapsulate it and to create the following method maker:
sub _top_it { my ($pkg, $methods) = @_; no strict 'refs'; foreach my $method (@$methods) { *{$pkg."::".$method} = do { my $m = $method; my $top = "_top_$m"; my $non = "_non_top_$m"; sub { my $self = shift; return $self->_is_top() ? $self->$top(@_) : $self->$non(@_) ; }; }; } return; }
Thus, when _is_top evaluates to true I call _top_mymethod and otherwise _non_top_mymethod. This is a variation on the "replace conditional with polymorphism" refactoring.
Now ->_current() returned the ->_current_idx()'th item from an internal stack representing the directories which the object is traversing. I wanted to see where "_current_idx" was set and discovered it was incremented when an item was pushed to the stack, and decremented when an item was popped. As a result, I eliminated "_current_idx" completely and replaced _current() with $self->_dir_stack()->[-1]. That removed a lot of cruft from the code.
I also was able to do what I wanted, and make sure the paths are maintained as the base path for the traversal followed by a list of extra components of each inner directory.
I noticed that I flat-copied the return of a method returning an array reference several times (E.g: [ @{$self->_components()} ]) and so created another method maker - this time for "_copy" methods.
And naturally, I extracted many methods.
All this enabled me to create ->next_obj() and ->item_obj() API methods that return objects instead of plain strings. Naturally, this is not refactoring, but extending.
While I was in the neighbourhood, I discovered that the code had a format-string-issue, which I fixed, and released File-Find-Object-0.1.1 immediately.
After the release, I received three failure reports from CPAN Testers. Two of them were for a missing dependency, that wasn't installed due to a bug in the tests' smoking setup. One of them, however, was for Win32, where it was a real bug. I was able to reproduce it using Strawberry Perl on my WinXP computer, and released File-Find-Object-0.1.2 that corrected it. The problem was that on non-cygwin-Win32 all inodes are returned as 0 in the calls to stat().
Today, I started writing the project that required all this work on File-Find-Object. So far it doesn't do much, but it's a start.